#include "GeneticAlgorithm.h"
#include "LinkerNodes.h"
#include "fProblem.h"
#include "Solution.h"
#include "Objective.h"
#include "LinkerParameters.h"
#include "SingleObjective.h"
#include "UniformIntDistribution.h"
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>       //std::vector
#include <algorithm>    //std::sort, std::for_each
#include <cassert>
#include <cmath>
#include <fstream> //ofstream
using namespace std;

const char* GeneticAlgorithm::name()const {
    return "GA";
}

const char* GeneticAlgorithm::description()const {
    return NULL;
}

void GeneticAlgorithm::parameters(LinkerParameters *com) throw (exception) {
    numero_individuos = com->Int("numero de individuos", 10);
}

void GeneticAlgorithm::services(LinkerNodes *com) throw (exception) {
    problem = com->get(&fProblem::obj, problem);
    init = com->need(init);
    cross = com->need(cross);
    mut = com->need(mut);
}

void GeneticAlgorithm::execute() throw (exception) {
    int count_avaliacoes = 0;
    int popStalling_counter = 0; //usado no critério de parada. Incrementa sempre que a população "parar de evoluir".

    // Alocação de memória
    best = problem->NewSolution();
    vector<Solution*> matingpool(numero_individuos / 2); //tem metade do tamanho da população
    vector<Solution*> offspring(numero_individuos); //tem o tamanho da população
    vector<Solution*> populacao;
    for (int i = 0; i < numero_individuos; i++) populacao.push_back(problem->NewSolution());

    //Inicia a população
    for (int i = 0; i < numero_individuos; i++) init->initialize(problem, populacao.at(i));

    //Avalia a população
    for (int i = 0; i < numero_individuos; i++) {
        problem->evaluate(populacao.at(i));
        count_avaliacoes++;
        if (*populacao.at(i) < *best) best->Copy(problem, populacao.at(i));
    }

    int corte1 = numero_individuos / 3; // índice dos primeiros 30% do vetor população
    int corte2 = numero_individuos * 9 / 10; // índice dos 90% da população

    do {

        /******************  PARENT SELECTION (ranking)  **************
         * Os primeiros (melhores) 30% da populacao -> matingpool
         * Os últimos (piores) 10% da populacao -> matingpool
         * Mais 10% aleatórios da populacao (fora das duas faixas anteriores) é
         * pega e -> matingpool.
         * No final das contas 50% da populacao vai para matingpool.
         */

        //ordena a população para rankeá-la. A função de comparação é chamada "lambda function"
        std::sort(populacao.begin(), populacao.end(), [](Solution * s1, Solution * s2) {
            return *s1 < *s2;
        });

        //populacao -> matingpool
        int idx_matingpool = 0;
        for (int i = 0; i < corte1; i++) matingpool.at(idx_matingpool++) = populacao.at(i);
        for (int i = corte2; i < numero_individuos; i++) matingpool.at(idx_matingpool++) = populacao.at(i);
        while (idx_matingpool < numero_individuos / 2) matingpool.at(idx_matingpool++) = populacao.at(UniformIntDistribution::rand(corte1, corte2));


        /*****************  CROSSOVER HEURÍSTICO + MUTAÇÃO GAUSSIANA   *****************
         * O vetor matingpool possui metade do tamanho do vetor populacao, e cada 2 pais geram
         * 1 filho. Queremos criar uma geração de filhos do tamanho da populacao original. Para tanto,
         * geramos 3 tipos de cruzamentos entre os indivíduos de matingpool:
         * 1o: cross entre os 2 melhores de matingpool, seguido do cross entre os próximos 2 melhores,
         *       e assim por diante. Isso gera numero_individuos/4 filhos.
         * 2o: cross entre o melhor e o pior, seguido do cross entre o segundo melhor e segundo pior,
         *       e assim por diante. Isso gera mais numero_individuos/4 filhos.
         * 3o: cross aleatório entre os indivíduos da matingpool, até gerar os numero_individuos/2
         *       faltantes.
         * No final das contas geramos o vetor offspring com o mesmo tamanho da populacao original.
         */

        //ordena a matingpool
        std::sort(matingpool.begin(), matingpool.end(), [](Solution * s1, Solution * s2) {
            return *s1 < *s2;
        });

        // realiza crossover + mutação para gerar o vetor offspring de tamanho = numero_individuos
        int i = 0;
        int idx_offspring = 0;
        do { // 1o cross
            assert(i + 1 < matingpool.size());
            Solution* child = cross->crossover(problem, matingpool.at(i), matingpool.at(i + 1));
            mut->mutation(problem, child);
            offspring.at(idx_offspring++) = child;
            i += 2;
        } while (i + 1 < matingpool.size());

        i = 0;
        do { // 2o cross
            Solution* child = cross->crossover(problem, matingpool.at(i), matingpool.at(matingpool.size() - 1 - i));
            mut->mutation(problem, child);
            offspring.at(idx_offspring++) = child;
            i++;
        } while (i < matingpool.size() / 2);

        while (idx_offspring < numero_individuos) { // 3o cross
            Solution* child;
            int i = UniformIntDistribution::rand(0, matingpool.size() - 1);
            int j = UniformIntDistribution::rand(0, matingpool.size() - 1);
            if (*matingpool.at(i) < *matingpool.at(j)) //pq crossover() exige que o primeiro Solution* seja o melhor.
                child = cross->crossover(problem, matingpool.at(i), matingpool.at(j));
            else
                child = cross->crossover(problem, matingpool.at(j), matingpool.at(i));
            mut->mutation(problem, child);
            offspring.at(idx_offspring++) = child;
        }
        //pronto. Agora o vetor offspring está gerado e contém exatamente o mesmo tamanho que o vetor populacao.


        /*********************** AVALIA OS FILHOS **********************
         *************************************************************/

        for (int i = 0; i < numero_individuos; i++) { // avalia offspring
            problem->evaluate(offspring.at(i));
            count_avaliacoes++;
        }

        std::sort(offspring.begin(), offspring.end(), [](Solution * s1, Solution * s2) { //ordena offspring
            return *s1 < *s2;
        });

        if (*offspring.at(0) < *best) { // verifica se o melhor indivíduo de offspring (primeiro elemento) é melhor que 'best'.
            best->Copy(problem, offspring.at(0));
            //            cout << "best = ";
            //            best->toString(cout);
            //            cout << endl;
        }

        // inserir os filhos na população:  merge(populacao, offspring) ==> populacao
        MergeInplace(populacao, offspring);

        if (isPopStalling(populacao)) //critério de parada
            popStalling_counter++;
    } while (popStalling_counter < POPSTALLINGMAXCOUNT);

    cout << "\n\nChecar arquivo\"/tmp/RESULTADOS.TXT\" para ver os resultados na forma (count_avaliacoes;minimo_global_encontrado)" << endl;
    cout << "(" << count_avaliacoes << ";" << dynamic_cast<SingleObjective*> (best->objective)->abs_value() << ")\n\n" << endl;

    ofstream myResultFile("/tmp/RESULTADOS.TXT", ios::app);
    myResultFile << count_avaliacoes << ";" << dynamic_cast<SingleObjective*> (best->objective)->abs_value() << endl;
    myResultFile.close();
}

void GeneticAlgorithm::results(LinkerResults * com) throw (exception) {
    best->objective->results(problem, com);
}

/**
 * Realiza merge(v1, v2) ==> v1. Os vetores v1 e v2 devem ter o mesmo tamanho. Esse método
 * se encarrega de liberar a memória dos objetos "Solution" que não forem escolhidos para ficar
 * em v1.
 * @param v1 vetor de Solution*. É o vetor destino.
 * @param v2 vetor de Solution*.
 */
void GeneticAlgorithm::MergeInplace(vector<Solution*> & v1, vector<Solution*> & v2) {
    int idx_v1 = 0;
    int idx_v2 = 0;
    int aux_v1 = 0;

    while (idx_v2 < numero_individuos && aux_v1 < numero_individuos) {
        if (*v2.at(idx_v2) < *v1.at(idx_v1)) { //se o filho for melhor que o pai, substitui o pai
            delete v1.at(idx_v1); //deleta o objeto
            v1.at(idx_v1++) = v2.at(idx_v2++);
            aux_v1 = idx_v1;
            continue;
        }
        idx_v1++;

        if (idx_v1 == numero_individuos) { //o filho não é melhor que ninguém na população
            delete v2.at(idx_v2++); //libera memória ocupada pelo objeto
            idx_v1 = ++aux_v1;
        }
    }
}

/**
 * Checa se o fitness da população como um todo está convergindo para o valor do fitness atual de
 * best. Isso mede diretamente se a população está uniforme, o que implica que a
 * população inteira está num mínimo local ou global.
 * @param pop vetor de Solution*.
 * @return true se a população está parando de evoluir (se está estagnando); false caso contrário.
 */
bool GeneticAlgorithm::isPopStalling(vector<Solution*> & pop) {
    double bestValue = dynamic_cast<SingleObjective*> (best->objective)->abs_value();
    double popWorstValue = dynamic_cast<SingleObjective*> (pop.at(pop.size() - 1)->objective)->abs_value();
    double diff = popWorstValue - bestValue;

    if (diff <= POPSTALLINGTHRESHOLD) return true;
    else return false;
}
